/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Chat;

import java.net.MalformedURLException;
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.*;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author danielsl & thorerikmidtb
 * "UnicastRemoteObject is used for exporting a remote object with JRMP and obtaining a stub that communicates to the remote object".
 * From http://download.oracle.com/javase/1.5.0/docs/api/java/rmi/server/UnicastRemoteObject.html
 * Listens to RMI requests and implements the interface which is used by the client to invoke remote methods.
 * 
 */
public class ServerImpl extends UnicastRemoteObject implements Server {

    Bot bot = new Bot();
    final String botName = "@Martleif";
    
    private ArrayList<Callback> userList = new ArrayList<Callback>();
    private ArrayList<Callback> adminList = new ArrayList<Callback>();
    private ArrayList<Callback> quizList = new ArrayList<Callback>();

    private int quizcheck = 0;
    int registrationClosedQuiz = 0;
    int quizNumber = 1;
    int quizOn = 0;
    boolean alreadyAnswered = false;

    Timer quizStartTimer = new Timer();
    Timer quizStartTimer2 = new Timer();
    Timer broadcastQuizQuestions = new Timer();

    private TimerTask timerTask = new TimerTask() {

        @Override
        public void run() {
            String registrationClosedBroadcast = bot.quizRegistrationClosedBroadcast();
            registrationClosedQuiz = 1;
            for (int i = 0; i < userList.size(); i++) {
                try {
                    userList.get(i).botMessage(registrationClosedBroadcast, botName);
                } catch (RemoteException ex) {
                    Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            broadcastQuizQuestions.scheduleAtFixedRate(timerTask3, 15000, 20000);
            String getReady = bot.quizSoonStarting();
            for (int i = 0; i < quizList.size(); i++) {
                try {
                    quizList.get(i).botMessage(getReady, botName);
                } catch (RemoteException ex) {
                    Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    };
    private TimerTask timerTask2 = new TimerTask() {

        @Override
        public void run() {
            String registrationSoonCloses = bot.quizRegistrationSoonCloses();
            for (int i = 0; i < userList.size(); i++) {
                try {
                    userList.get(i).botMessage(registrationSoonCloses, botName);
                } catch (RemoteException ex) {
                    Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    };
    private TimerTask timerTask3 = new TimerTask() {

        @Override
        public void run() {
            if (quizNumber <= 8 && !quizList.isEmpty()) {
                try {
                    broadcastQuiz();
                } catch (RemoteException ex) {
                    Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            } else {
                try {
                    quizResults();
                    for (int i = 0; i < quizList.size(); i++) {
                        quizList.get(i).deleteQuizScore();
                    }
                    quizcheck = 0;
                    registrationClosedQuiz = 0;
                    quizNumber = 1;
                    quizList.clear();
                } catch (RemoteException ex) {
                    Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    };

    /**
     * Empty constructor.
     * @throws RemoteException
     */
    public ServerImpl() throws RemoteException {
    }

    /**
     * This method is used by clients connecting to the server, broadcasting to everyone that there is a new user online.
     * @param c
     * @throws RemoteException
     */
    public synchronized void connect(Callback c) throws RemoteException {
        if (usernameTaken(c.getUserName())) {
            c.botMessage(bot.nameTaken(c.getUserName()), botName);
            c.setUserName(c.getUserName() + "1");
        }
        userList.add(c);
        for (int g = 0; g < userList.size(); g++) {
            Callback user = userList.get(g);
            user.connectMessage(c.getUserName());
        }
        getClientList();
    }

    /**
     * This method is used by the client when they want to change their user name.
     * @param newName
     * @param c
     * @throws RemoteException
     */
    public synchronized void changeName(String newName, Callback c) throws RemoteException {
        String oldName = c.getUserName();
        for (int i = 0; i < userList.size(); i++) {
            Callback user = userList.get(i);
            if (user.getUserName().equals(c.getUserName()) && !usernameTaken(newName)) {
                oldName = c.getUserName();
                c.setUserName(newName);
            } else if (user.getUserName().equals(c.getUserName()) && usernameTaken(newName)) {
                c.botMessage(bot.nameTaken(newName), botName);
                c.setUserName(newName + "1");
            }
            user.rename(newName, oldName);
            getClientList();
        }
    }

    /**
     * This method is the backbone of all the servers methods. This will take all input from the users, and broadcast it to everyone on the server. However, if the users are typing in commands,
     * the correct method for that command will be used.
     * @param message
     * @param c
     * @throws RemoteException
     */
    public synchronized void broadcast(String message, Callback c) throws RemoteException {
        if (!message.trim().isEmpty() && !message.trim().contains("/help") && !message.trim().contains("/opme") && !message.trim().contains("/uptime")
                && !message.trim().contains("/rename") && !message.trim().contains("/kick") && !message.trim().contains("/quizstart") && !message.trim().contains("/quizjoin")
                && !message.trim().contains("/quizanswer") && !message.trim().contains("/quizquit") && !message.trim().contains("/opwho")) {
            for (int i = 0; i < userList.size(); i++) {
                Callback user = userList.get(i);
                user.broadcastMessage(message, c.getUserName());
            }
        } else if (message.trim().contains("/help")) {
            this.getHelpFromBot(c);
        } else if (message.trim().contains("/opme")) {
            String passcode = message.substring(6).trim();
            this.getOPFromBot(passcode, c);
        } else if (message.trim().startsWith("/uptime")) {
            this.uptimeFromBot(c);
        } else if (message.trim().contains("/rename")) {
            String newName = message.substring(8).trim();
            this.changeName(newName, c);
        } else if (message.trim().contains("/kick")) {
            String nameToKick = message.substring(6).trim();
            this.kick(nameToKick, c);
        } else if (message.trim().contains("/quizstart")) {
            this.startQuiz(c);
        } else if (message.trim().contains("/quizjoin")) {
            this.joinQuiz(c);
        } else if (message.trim().contains("/quizanswer")) {
            String answer = message.substring(12).trim();
            this.checkAnswerQuiz(answer, c);
        } else if (message.trim().contains("/quizquit")) {
            this.quizQuit(c);
        } else if (message.trim().contains("/opwho")) {
            this.whoIsOP(c);
        }
    }

    /**
     * This method will be used by the clients when they want to speak to someone in private. This method will also open up a new tab on the receiving end, away from the main chat area.
     * @param message
     * @param whisperTo
     * @param c
     * @throws RemoteException
     */
    public synchronized void whisper(String message, String whisperTo, Callback c) throws RemoteException {
        if (!message.trim().isEmpty() && !message.trim().startsWith("/")) {
            for (int i = 0; i < userList.size(); i++) {
                Callback user = userList.get(i);

                if (user.getUserName().equals(whisperTo) && !user.tabAlreadyOpened(c.getUserName()) && userOnline(whisperTo)) {
                    user.receiveNewTab(c.getUserName());
                    user.whisperMessage(message, whisperTo, c.getUserName());
                    c.whisperMessage(message, whisperTo, "You");
                    break;
                } else if (user.getUserName().equals(whisperTo) && user.tabAlreadyOpened(c.getUserName())
                        && c.tabAlreadyOpened(user.getUserName()) && userOnline(whisperTo)) {
                    user.whisperMessage(message, whisperTo, c.getUserName());
                    c.whisperMessage(message, whisperTo, "You");
                    break;
                }
            }
            if (c.tabAlreadyOpened(whisperTo) && !userOnline(whisperTo)) {
                c.whisperMessage(bot.userNotHere(whisperTo), "", botName);
                }
            }

        }

    /**
     * This method will start up the whole quiz.
     * @param c
     * @throws RemoteException
     */
    public synchronized void startQuiz(Callback c) throws RemoteException {
        if (adminList.isEmpty()) {
            String notOP = bot.notOP();
            c.botMessage(notOP, botName);
        }
        if (adminList.contains(c) && quizcheck == 0 && quizOn == 0) {
            for (int i = 0; i < userList.size(); i++) {
                Callback allUsers = userList.get(i);
                String broadcastquiz = bot.quizStart(c.getUserName());
                allUsers.botMessage(broadcastquiz, botName);
            }
            quizcheck = 1;
            quizOn = 1;
            quizStartTimer.schedule(timerTask, 60000);
            quizStartTimer2.schedule(timerTask2, 30000);
        } else if (adminList.contains(c) && quizcheck == 0 && quizOn == 1) {
            for (int i = 0; i < userList.size(); i++) {
                Callback allUsers = userList.get(i);
                String broadcastquiz = bot.quizStart(c.getUserName());
                allUsers.botMessage(broadcastquiz, botName);
            }
            quizcheck = 1;
        } else if (quizcheck == 1 && adminList.contains(c)) {
            String quizError = bot.quizError();
            c.botMessage(quizError, botName);
        } else if (!adminList.contains(c) && !adminList.isEmpty()) {
            String notOP = bot.notOP();
            c.botMessage(notOP, botName);
        }
    }

    /**
     * This method will be used by the clients if they want to join in on the quiz, if it is open for registration.
     * @param c
     * @throws RemoteException
     */
    public synchronized void joinQuiz(Callback c) throws RemoteException {
        if (quizcheck == 1 && !quizList.contains(c) && registrationClosedQuiz == 0) {
            quizList.add(c);
            String quizJoined = bot.quizJoined();
            c.botMessage(quizJoined, botName);
        } else if (quizcheck == 0) {
            String quizJoinedError = bot.quizJoinedError();
            c.botMessage(quizJoinedError, botName);
        } else if (quizList.contains(c)) {
            String alreadyJoinedError = bot.quizAlreadyJoined();
            c.botMessage(alreadyJoinedError, botName);
        } else if (registrationClosedQuiz == 1) {
            String registrationClosed = bot.quizRegistrationClosed();
            c.botMessage(registrationClosed, botName);
        }
    }

    /**
     * This method will broadcast the quiz questions to the people who registered for the quiz.
     * @throws RemoteException
     */
    public synchronized void broadcastQuiz() throws RemoteException {
        for (int i = 0; i < quizList.size(); i++) {
            quizList.get(i).botMessage(bot.quizQuestion(quizNumber), botName);
        }
        alreadyAnswered = false;
        quizNumber++;
    }

    /**
     * This method will be used to check if the answer given from the client is correct or not. The bot have the question and answers, so it will be checked with the bot object, wether it is correct or not.
     * @param answer
     * @param c
     * @throws RemoteException
     */
    public synchronized void checkAnswerQuiz(String answer, Callback c) throws RemoteException {
        String botAnswer = bot.quizAnswer(quizNumber);
        if (quizcheck == 1 && quizList.contains(c) && botAnswer.equalsIgnoreCase(answer) && alreadyAnswered == false) {
            c.botMessage(bot.quizYourAnswer(answer), botName);
            c.setQuizScore();
            alreadyAnswered = true;
        } else if (quizcheck == 1 && quizList.contains(c) && !botAnswer.equalsIgnoreCase(answer) && alreadyAnswered == false) {
            c.botMessage(bot.quizWrongAnswer(answer), botName);
        } else if (quizcheck == 0) {
            c.botMessage(bot.quizJoinedError(), botName);
        } else if (!quizList.contains(c)) {
            c.botMessage(bot.quizJoinedError2(), botName);
        } else if (alreadyAnswered == true) {
            c.botMessage(bot.quizAlreadyAnswered(), botName);
        }
    }

    /**
     * This method will print out the results from the quiz, a leaderboard so to say.
     * @throws RemoteException
     */
    public synchronized void quizResults() throws RemoteException {
        String results = "The results of the quiz is as follows: \n";
        for (int i = 0; i < quizList.size(); i++) {
            results += "User: " + quizList.get(i).getUserName() + ". Points : " + quizList.get(i).getQuizScore() + ".\n";
        }
        results += "Congratulations to the winner, and better luck next time for everyone else! Quiz-o-tron: OUT!";
        for (int i = 0; i < quizList.size(); i++) {
            quizList.get(i).botMessage(results, botName);
        }
    }

    /**
     * This method will stop the quiz in progress, resetting everything.
     * @param c
     * @throws RemoteException
     */
    public synchronized void quizQuit(Callback c) throws RemoteException {
        if (adminList.contains(c) && quizcheck == 1) {
            for (int i = 0; i < quizList.size(); i++) {
                quizList.get(i).deleteQuizScore();
            }
            quizcheck = 0;
            registrationClosedQuiz = 0;
            quizNumber = 1;
            quizList.clear();
            for (int i = 0; i < userList.size(); i++) {
                userList.get(i).botMessage(bot.quizStop(c.getUserName()), botName);
            }
        } else if (quizcheck == 0) {
            c.botMessage(bot.quizJoinedError(), botName);
        } else if (!adminList.contains(c)) {
            c.botMessage(bot.notOP(), botName);
        }
    }

    /**
     * This method will ask the bot object for help, listing out commands to the users which they can use in the chat.
     * @param c
     * @throws RemoteException
     */
    public synchronized void getHelpFromBot(Callback c) throws RemoteException {
        String help = bot.help();
        c.botMessage(help, botName);
    }

    /**
     * This method will ask the bot object for how long the server has been up.
     * @param c
     * @throws RemoteException
     */
    public synchronized void uptimeFromBot(Callback c) throws RemoteException {
        String uptime = bot.uptime();
        c.botMessage(uptime, botName);

    }

    /**
     * This method will give you OP rights, if you have the correct password.
     * @param passcode
     * @param c
     * @throws RemoteException
     */
    public synchronized void getOPFromBot(String passcode, Callback c) throws RemoteException {
        if (!adminList.contains(c)) {
            for (int i = 0; i < userList.size(); i++) {
                Callback user = userList.get(i);
                if (bot.getOP(passcode) == true && user.getUserName().equals(c.getUserName())) {
                    adminList.add(c);
                } else if (c.getUserName().equals(user.getUserName()) && bot.getOP(passcode) == false) {
                    String error = bot.errorOP(passcode);
                    user.botMessage(error, botName);
                }
                String okPassed = bot.okOP(c.getUserName());
                user.botMessage(okPassed, botName);
            }
        } else {
            String alreadyAdmin = bot.alreadyOP();
            c.botMessage(alreadyAdmin, botName);
        }
    }

    /**
     * This method will list out all OP on the server
     * @param c
     * @throws RemoteException
     */
    public synchronized void whoIsOP(Callback c) throws RemoteException {
        adminList.trimToSize();
        String OP = "These are OP on this server: \n";
        if (adminList.isEmpty()) {
            c.botMessage(bot.noOPHere(), botName);
        } else {
            for (int i = 0; i < adminList.size(); i++) {
                OP += adminList.get(i).getUserName() + "\n";
            }
            c.botMessage(OP, botName);
        }
    }

    /**
     * This method will be used, if you have the correct rights for it(you need to be OP), to kick people.
     * @param userToKick
     * @param c
     * @throws RemoteException
     */
    public synchronized void kick(String userToKick, Callback c) throws RemoteException {
        if (adminList.contains(c)) {
            for (int i = 0; i < userList.size(); i++) {
                Callback user = userList.get(i);
                if (user.getUserName().equals(userToKick)) {
                    user.botMessage(bot.kick(c.getUserName()), botName);
                    user.getKicked();

                } else if (user.getUserName().equals(userToKick)) {
                    c.botMessage(bot.noneToKick(userToKick), botName);
                }
                user.botMessage(bot.userKicked(userToKick, c.getUserName()), botName);
            }
        } else if (!adminList.contains(c)) {
            c.botMessage(bot.notOP(), botName);
        }
    }

    /**
     * This method will be called by a client when they are disconnecting/quitting from the server. When that is done, the method will be calling the getClientList() to refresh the client list.
     * @param c
     * @throws RemoteException
     */
    public synchronized void quit(Callback c) throws RemoteException {
        userList.remove(c);
        adminList.remove(c);
        quizList.remove(c);
        for (int i = 0; i < userList.size(); i++) {
            Callback user = userList.get(i);
            user.quitMessage(c.getUserName());
        }
        getClientList();
    }

    /**
     * Checks the userList, to see if the name used by a client, is already taken or not
     * @param tryName
     * @return true if the username is taken, false otherwise.
     * @throws RemoteException
     */
    public boolean usernameTaken(String tryName) throws RemoteException {
        boolean alreadyTaken = false;
        if (!userList.isEmpty()) {
            for (int i = 0; i < userList.size(); i++) {
                if (userList.get(i).getUserName().equals(tryName)) {
                    alreadyTaken = true;
                    break;
                } else {
                    alreadyTaken = false;
                }
            }
        }
        return alreadyTaken;
    }

    /**
     * Checks if the user is online(registered in the userList)
     * @param userName
     * @return true if the user is online, false otherwise.
     * @throws RemoteException
     */
    public boolean userOnline(String userName) throws RemoteException {
        boolean userOnline = false;
        for (int i = 0; i < userList.size(); i++) {
            if (userList.get(i).getUserName().equals(userName)) {
                userOnline = true;
                break;
            }
        }
        return userOnline;
    }

    /**
     * This method will give an ArrayList of String objects of all the users connected to the server.
     * @throws RemoteException
     */
    public synchronized void getClientList() throws RemoteException {
        ArrayList<String> newList = new ArrayList<String>();
        newList.add(0, botName);
        for (int i = 0; i < userList.size(); i++) {
            newList.add(userList.get(i).getUserName());
        }
        for (int i = 0; i < userList.size(); i++) {
            userList.get(i).getClientList(newList);
        }
    }

    /*
     * The main method, where all the magic happens!
     */
    public static void main(String[] args) throws Exception {
        System.setProperty("java.rmi.server.hostname", "127.0.0.1");
        /*
         * Sun recommends a SecurityManager when we operate with remote objects!
         * Please refer to http://download.oracle.com/javase/tutorial/rmi/implementing.html for why.
         */
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        try {
            LocateRegistry.createRegistry(1099);
            ServerImpl server = new ServerImpl();
            Naming.rebind("//127.0.0.1/ServerImpl", server);
            javax.swing.JOptionPane.showMessageDialog(null, "If you push this button down here, you WILL TERMINATE THIS SERVER!");
            Naming.unbind("//127.0.0.1/ServerImpl");
            System.exit(0);
        } catch (MalformedURLException mal) {
            System.out.println("UH OH! We got an error here(1): " + mal);
        } catch (RemoteException re) {
            System.out.println("UH OH! We got an error here(2): " + re);
        } catch (NotBoundException bound) {
            System.out.println("UH OH! We got an error here(3): " + bound);
        } catch (Exception e) {
            System.out.println("UH OH! We got an error here(4): " + e);
        }
    }
}
//http://java.sun.com/developer/onlineTraining/Programming/JDCBook/appA.html#SocketPermission Use that link for securitymanager :D
//http://forums.oracle.com/forums/thread.jspa?threadID=1175999 also this
// -Djava.security.debug=access,failure
//-Djava.security.policy=/Users/thorerikmidtb/NetBeansProjects/rmi-project/src/Chat/server.policy

